package com.jxtc.enterprise.merchant.controller;


import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.jxtc.enterprise.common.constants.FoodMenuMakerEnum;
import com.jxtc.enterprise.common.constants.FoodMenuStatus;
import com.jxtc.enterprise.common.constants.OrderPaymentStatus;
import com.jxtc.enterprise.common.entity.*;
import com.jxtc.enterprise.common.mapper.*;
import com.jxtc.enterprise.common.utils.Result;
import com.jxtc.enterprise.common.utils.ResultCode;
import com.jxtc.enterprise.common.utils.ResultGenerator;
import com.jxtc.enterprise.merchant.service.MerchantStoreManageService;
import com.jxtc.enterprise.merchant.vo.LoginSuccessRespVO;
import com.jxtc.enterprise.employee.serviceImpl.JxOrderServiceImpl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;

import java.text.MessageFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 小程序 商户登录页面 接口，当前只有 登录一个接口
 */

@RestController
@RequestMapping("/jxtc/enterprise/merchant")
@RequiredArgsConstructor
@Slf4j
public class MerchantLoginPageController {

    @Autowired
    private StoreMapper storeMapper;

    @Autowired
    private FoodMenuMapper foodMenuMapper;

    @Autowired
    private CompanyMapper companyMapper;

    @Autowired
    private OrderDetailMapper orderDetailMapper;

    @Autowired
    private JxOrderServiceImpl orderServiceImpl;

    @Autowired
    private MenuStoreConfigMapper menuStoreConfigMapper;

    @Autowired
    private MerchantStoreManageService merchantStoreManageService;

    /**
     * 当某一次请求调用多个方法时，各个方法中执行各自不同的步骤，可以在请求入口，controller方法生成一个 uuid字符串，作为本次请求的唯一标识符放在 ThreadLocal中
     * 本次请求经过的每个方法调用中，都可以获取到此uuid，打印日志中带上此uuid，这样最终在日志文件中，可以通过此uuid来将 分散在不同位置的日志串起来，用来定位问题
     */
    private final ThreadLocal<String> threadLocal = new ThreadLocal<>();

    /**
     * 20.1 商户通过 手机号+密码 登录
     * 登录后返回该店铺为那些公司供餐，返回公司列表，用来作为【订单】页面顶部下拉列表的数据
     *
     * @param requestBody 商户手机号和密码
     * @return
     */
    @PostMapping("/loginByAccountPassword")
    Result<?> loginByAccountPassword(@RequestBody JSONObject requestBody) {

        String requestId = UUID.randomUUID().toString();
        threadLocal.set(requestId);
        log.info("requestId:{}, /jxtc/enterprise/merchant/loginByAccountPassword, merchant phone: {}", requestId, requestBody.getString("phone"));
        String phone = requestBody.getString("phone");
        String password = requestBody.getString("password");
        Result<Object> result = new Result<>();

        List<Store> storeInfos = storeMapper.selectList(
                new LambdaQueryWrapper<Store>()
                        .eq(Store::getDelFlag, false)
                        .eq(Store::getMerchantPhone, phone));

        if (CollectionUtils.isEmpty(storeInfos)) {
            result.setCode(ResultCode.INTERNAL_SERVER_ERROR);
            result.setMessage("手机号: " + phone + " , 未录入, 请联系管理员进行处理");
            result.setData(null);
            return result;
        } else if (storeInfos.size() > 1) {
            result.setCode(ResultCode.INTERNAL_SERVER_ERROR);
            result.setMessage("有多个商户的手机号为: " + phone + ", 请联系管理员进行处理");
            result.setData(null);
            return result;
        } else {
            if (password.equalsIgnoreCase(storeInfos.get(0).getMerchantLoginPasswd())) {
                // 返回商户供餐的公司 名称，id，店铺id；由于返回多个公司，默认列表中第一个为 【订单】页面展示的公司；
                // 当登录成功后，前端接着调用 queryStoreOrderDetails 接口，传递 店铺id 和 公司列表中第一个公司的id
                // 登录成功后，跳转到【订单】页面，并展示 公司列白中第一个公司的 订单信息
                String storeId = storeInfos.get(0).getId();
                String storeName = storeInfos.get(0).getName();

                // 店铺给那些公司供餐，返回被供餐的 公司的id和名称 列表; 查询时，需要查询菜单状态为 启用中的，虽然某个菜单上有该店铺，但是该菜单禁用了，商户登录商户小程序时，也不用显示该菜单对应的公司
                List<FoodMenu> suppliedCompanys = foodMenuMapper.selectList(
                        new LambdaQueryWrapper<FoodMenu>()
                                .eq(FoodMenu::getDelFlag, false)
                                .eq(FoodMenu::getMarker, FoodMenuMakerEnum.STORE_MARKER.getCode())
                                .eq(FoodMenu::getStoreId, storeId)
                                .eq(FoodMenu::getStatus, FoodMenuStatus.ACTIVE.getStatus()));
                List<LoginSuccessRespVO.CompanyInfo1> companys = new ArrayList<>();
                List<String> suppliedCompanyIds =
                        suppliedCompanys.stream()
                                .map(FoodMenu::getCompanyId)
                                // 一个店铺可以为某个公司公司多个时段的用餐，因此上面sql根据店铺id查询出来的多条记录，公司id可能会相同，需要去重
                                .distinct().collect(Collectors.toList());
                // 店铺为多个公司供餐，对获取每个公司的基本信息，返回给商户小程序
                for (String companyId : suppliedCompanyIds) {
                    LoginSuccessRespVO.CompanyInfo1 companyInfo = new LoginSuccessRespVO.CompanyInfo1();
                    companyInfo.setCompanyId(companyId);
                    // todo 此处可优化，可以将公司信息缓存到redis中，从redis中获取公司名称
                    Company company = companyMapper.selectOne(
                            new LambdaQueryWrapper<Company>()
                                    .eq(Company::getDelFlag, false)
                                    .eq(Company::getId, companyId)
                                    .eq(Company::getStopFlag, 0));
                    companyInfo.setCompanyName(company.getName());

                    companys.add(companyInfo);
                }

                LoginSuccessRespVO loginSuccessVO = new LoginSuccessRespVO();
                loginSuccessVO.setStoreId(storeId);
                loginSuccessVO.setStoreName(storeName);
                loginSuccessVO.setSupplyCompanyList(companys);

                result.setCode(ResultCode.SUCCESS);
                result.setMessage("success");
                result.setData(loginSuccessVO);
                log.warn("requestId: {}, {}", threadLocal.get(), result);
                return result;
            } else {
                result.setCode(ResultCode.UNAUTHORIZED);
                result.setMessage("账号或密码错误");
                result.setData(null);

                return result;
            }
        }
    }

    /**
     * 20.2 查询某个公司的员工在该店铺中的订单聚合统计信息
     *
     * @param storeId
     * @param companyId
     * @return
     */
    @GetMapping("/queryStoreOrdersForOneCompany")
    Result<?> queryStoreOrdersForOneCompany(String storeId, String companyId, String mealType, @RequestParam(name = "phone", required = false) String phone) {
        String requestId = UUID.randomUUID().toString();
        threadLocal.set(requestId);
        log.info("requestId:{}, /jxtc/enterprise/merchant/queryStoreOrdersForOneCompany, storeId: {}, companyId: {}, mealType: {}, phone: {}", requestId, storeId, companyId, mealType, phone);

        if (StringUtils.isBlank(storeId)) {
            // 如果前端传递过来的 storeId 为空，则根据店主手机号获取该店铺的 ID
            storeId = merchantStoreManageService.queryStoreIdByMerchantPhone(phone);
        }

        List<OrderDetail> foodCountMap = orderDetailMapper.selectList(
                new QueryWrapper<OrderDetail>()
                        // 由于查询返回的结果集中的数据项，类型必须是 OrderDetail，因此 sum(quantity) 求和后的值，仍然存放在 quantity 中
                        .select("food_id as foodId, food_print_name as foodPrintName, sum(quantity) as quantity")
                        .lambda()
                        .eq(OrderDetail::getStoreId, storeId)
                        .eq(OrderDetail::getCompanyId, companyId)
                        // 查询用户在该店铺，为某个用餐时段选购的所有菜品
                        .eq(OrderDetail::getMealType, mealType)
                        // 展示 待发货 或 待收货的菜品，按照菜品名聚合后的数据
                        .in(OrderDetail::getStatus, OrderPaymentStatus.TO_SEND.getValue(), OrderPaymentStatus.TO_RECEIVE.getValue())
                        .groupBy(OrderDetail::getFoodPrintName, OrderDetail::getFoodId));

        // 按照份数，降序排序
        foodCountMap.sort(Comparator.comparingInt(OrderDetail::getQuantity).reversed());

        int totalCount = 0;
        // 只需要返回各个菜品的名称，id，份数即可
        List<JSONObject> formattedFoodCountList = new ArrayList<>();
        for (OrderDetail item : foodCountMap) {
            JSONObject formattedFoodInfo = new JSONObject();
            formattedFoodInfo.put("foodId", item.getFoodId());
            formattedFoodInfo.put("foodPrintName", item.getFoodPrintName());
            formattedFoodInfo.put("count", item.getQuantity());
            totalCount += item.getQuantity();

            formattedFoodCountList.add(formattedFoodInfo);
        }

        JSONObject resultData = new JSONObject();
        resultData.put("totalCount", totalCount);
        resultData.put("foodPrintNameList", formattedFoodCountList);

        Result<Object> result = new Result<>();
        result.setCode(ResultCode.SUCCESS);
        result.setMessage("success");
        result.setData(resultData);

        log.info("requestId:{}, response: {}", requestId, result);

        return result;
    }


    /**
     * 20.3
     * 使用 foodPrintName 来进行查询，有规格的套餐，每个订单的 food_print_name 会带各个规格的具体 选中值
     * 非规格套餐，food_print_name 为套餐的名称
     *
     * @param requestBody
     * @return
     */
    @PostMapping("/queryStoreOrdersByFoodName")
    Result<?> queryOrderDetailsByFoodName(@RequestBody JSONObject requestBody) {
        String storeId = requestBody.getString("storeId");
        String companyId = requestBody.getString("companyId");
        String foodPrintName = requestBody.getString("foodPrintName");
        String mealType = requestBody.getString("mealType");
        String phone = requestBody.getString("phone");
        if (StringUtils.isBlank(storeId)) {
            // 如果前端传递过来的 storeId 为空，则根据店主手机号获取该店铺的 ID
            storeId = merchantStoreManageService.queryStoreIdByMerchantPhone(phone);
        }

        Result<Object> result = new Result<>();
        result.setCode(ResultCode.SUCCESS);
        result.setMessage("success");
        if (StringUtils.isNoneBlank(foodPrintName)) {
            // 若 foodPrintName 不为空，表示查询某一个菜品的具体订购详情
            List<HashMap<String, Object>> orderDetailsByFoodPrintName = foodMenuMapper.queryFoodOrderDetailsByFoodPrintName(companyId, storeId, foodPrintName, mealType);
            orderDetailsByFoodPrintName.forEach(item -> {
                // 当某个同样的菜品点了多份时，商户小程序上按照 【x 2份】的效果，显示每个餐品的实际份数
                String foodOriginalPrintName = (String) item.get("foodPrintName");
                Integer count = (Integer) item.get("count");
                item.remove("foodPrintName");
                item.put("foodPrintName", MessageFormat.format("{0}     【x {1}份】", foodOriginalPrintName, count));

                LocalDateTime orderTime = (LocalDateTime) item.get("orderCreateTime");
                String formattedOrderCreateTime = orderTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
                item.remove("orderCreateTime");
                item.put("orderCreateTime", formattedOrderCreateTime);
            });
            result.setData(orderDetailsByFoodPrintName);
        } else {
            // todo 此分支是否有意义？？？
            // 若 foodPrintName 为空，表示某个公司员工在该店铺订购的所有订单
            List<HashMap<String, Object>> allOrderDetails = foodMenuMapper.queryAllFoodOrderDetailsByCompanyIdAndStoreId(companyId, storeId);
            allOrderDetails.forEach(item -> {
                LocalDateTime orderTime = (LocalDateTime) item.get("orderCreateTime");
                String formattedOrderCreateTime = orderTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
                item.remove("orderCreateTime");
                item.put("orderCreateTime", formattedOrderCreateTime);
            });
            result.setData(allOrderDetails);
        }

        return result;
    }

    /**
     * 20.4 根据店铺id，查询那些公司在该店铺订购餐品，返回公司列表
     *
     * @param storeId
     * @return
     */
    @GetMapping("/querySuppliedCompanysByPhone")
    Result<?> querySuppliedCompanysByPhone(@RequestParam(value = "phone", required = false) String phone, @RequestParam("storeId") String storeId) {

        String requestId = UUID.randomUUID().toString();
        threadLocal.set(requestId);
        log.info("requestId:{}, /jxtc/enterprise/merchant/querySuppliedCompanysByPhone, merchant phone: {}, storeId: {}", requestId, phone, storeId);

        if(StringUtils.isBlank(storeId)) {
            // 当storeId为空时，根据手机号查询得到 店铺id
            List<Store> stores = storeMapper.selectList(new LambdaQueryWrapper<Store>().eq(Store::getMerchantPhone, phone).eq(Store::getDelFlag, false));
            if (CollectionUtils.isEmpty(stores)) {
                log.error("querySuppliedCompanysByPhone, can't find store by phone, phone: {}", phone);
            } else {
                storeId = stores.get(0).getId();
            }
        }

        // 返回商户供餐的公司 名称，id，店铺id；由于返回多个公司，默认列表中第一个为 【订单】页面展示的公司；
        // 当登录成功后，前端接着调用 queryStoreOrderDetails 接口，传递 店铺id 和 公司列表中第一个公司的id
        // 登录成功后，跳转到【订单】页面，并展示 公司列白中第一个公司的 订单信息
        Result<Object> result = new Result<>();
        // 店铺给那些公司供餐，返回被供餐的 公司的id和名称 列表
        List<FoodMenu> suppliedCompanys = foodMenuMapper.selectList(
                new LambdaQueryWrapper<FoodMenu>()
                        .eq(FoodMenu::getDelFlag, false)
                        .eq(FoodMenu::getMarker, FoodMenuMakerEnum.STORE_MARKER.getCode())
                        .eq(FoodMenu::getStoreId, storeId)
                        .eq(FoodMenu::getStatus, FoodMenuStatus.ACTIVE.getStatus()));
        // 查询某个店铺为所有公司的各个用餐时段配置的记录;后续代码按照公司进行分组，返回该店铺为各个公司配置的用餐时段
        List<MenuStoreConfig> menuStoreConfigForAllCompanys = menuStoreConfigMapper.selectList(
                new LambdaQueryWrapper<MenuStoreConfig>()
                        .eq(MenuStoreConfig::getStoreId, storeId)
                        .eq(MenuStoreConfig::getDelFlag, 0));

        // key为公司id，value为店铺针对公司各个用餐时段的配置，如果公司员工多个用餐时段在该店铺点餐，则表中该店铺针对该公司的每个用餐时段都有一行记录
        Map<String, List<MenuStoreConfig>> companyIdToMealTypesConfig =
                menuStoreConfigForAllCompanys.stream().collect(Collectors.groupingBy(MenuStoreConfig::getCompanyId));
        List<String> suppliedCompanyIds =
                suppliedCompanys.stream()
                        .map(FoodMenu::getCompanyId)
                        // 一个店铺可以为某个公司公司多个时段的用餐，因此上面sql根据店铺id查询出来的多条记录，公司id可能会相同，需要去重
                        .distinct().collect(Collectors.toList());
        // 获取启用的 menuId 列表
        List<String> enabledMenuIdList = suppliedCompanys.stream()
                .map(FoodMenu::getMenuId)
                .distinct()
                .collect(Collectors.toList());
        List<LoginSuccessRespVO.CompanyInfo1> companys = new ArrayList<>();
        for (String companyId : suppliedCompanyIds) {
            LoginSuccessRespVO.CompanyInfo1 companyInfo = new LoginSuccessRespVO.CompanyInfo1();
            companyInfo.setCompanyId(companyId);
            // todo 此处可优化，可以将公司信息缓存到redis中，从redis中获取公司名称
            Company company = companyMapper.selectOne(
                    new LambdaQueryWrapper<Company>()
                            .eq(Company::getDelFlag, false)
                            .eq(Company::getId, companyId)
                            .eq(Company::getStopFlag, 0));
            if (company == null) {
                // 如果根据 companyId 查不到对应的公司，表明该条记录为脏数据，直接跳过即可
                log.error("querySuppliedCompanysByPhone, food_menu 中记录的 company_id 在公司表中不存在, companyId: {}", companyId);
                continue;
            }
            companyInfo.setCompanyName(company.getName());
            // 公司员工多个用餐时段在同一个店铺内用餐，针对每个用餐时段都有一个独立的配置
            List<MenuStoreConfig> menuStoreConfigsForOneCompany = companyIdToMealTypesConfig.get(companyId);
            List<String> mealTypesForOneCompany = menuStoreConfigsForOneCompany
                    .stream()
                    // 仅返回启用的菜单 ID 对应的配置
                    .filter(menuStoreConfig -> enabledMenuIdList.contains(menuStoreConfig.getMenuId()))
                    .map(MenuStoreConfig::getMealType)
                    // 按照字段序对列表中的各个字符串进行排序，按字典项排序
                    .sorted(String::compareTo)
                    .collect(Collectors.toList());
            // 结果填充到返回值中
            companyInfo.setMealTypes(mealTypesForOneCompany);

            companys.add(companyInfo);
        }

        result.setCode(ResultCode.SUCCESS);
        result.setMessage("success");
        result.setData(companys);
        log.warn("{}", result);
        return result;
    }

    /**
     * 20.5 商户小程序，点击某个订单上的 【打印订单】 按钮，立即打印一次订单
     *
     * @param orderId
     * @return
     */
    @GetMapping("/printByOrderId")
    Result<?> printByOrderId(@RequestParam("orderId") String orderId) {

        log.warn("手动触发立即打印订单, 订单id: {}", orderId);

        Result<Object> result = new Result<>();
        orderServiceImpl.printOrder(orderId);

        result.setCode(ResultCode.SUCCESS);
        result.setMessage("success");
        result.setData("操作成功，正在打印中 ...");
        log.warn("{}", result);
        return result;
    }

    /**
     * 20.6 商户小程序，点击 【全部打印】 按钮，立即重新打印所有订单
     *
     * @param companyId
     * @return
     */
    @GetMapping("/printAllOrders")
    Result<?> printAllOrders(@RequestParam("companyId") String companyId, @RequestParam("storeId") String storeId, @RequestParam("mealType") String mealType, @RequestParam(name = "phone", required = false) String phone) {
        log.info("printAllOrders, companyId: {}, storeId: {}, mealType: {}, phone: {}", companyId, storeId, mealType, phone);
        if (StringUtils.isBlank(storeId)) {
            // 如果前端传递过来的 storeId 为空，则根据店主手机号获取该店铺的 ID
            storeId = merchantStoreManageService.queryStoreIdByMerchantPhone(phone);
        }

        LambdaQueryWrapper<OrderDetail> queryWrapper = new LambdaQueryWrapper<>();
        // 待发货 或 待收货的菜品
        queryWrapper.select(OrderDetail::getOrderId)
                .eq(OrderDetail::getCompanyId, companyId).eq(OrderDetail::getStoreId, storeId).eq(OrderDetail::getMealType, mealType)
                .in(OrderDetail::getStatus, OrderPaymentStatus.TO_SEND.getValue(), OrderPaymentStatus.TO_RECEIVE.getValue());
        List<OrderDetail> orderDetails = orderDetailMapper.selectList(queryWrapper);
        // 同一份订单，可以有多个餐品，因此，从 order_detail 表中查询出来的orderId 会存在重复的；比如 有2份订单，orderId1， orderId2，orderId1 有2份菜品，orderId2有3份菜品
        // 则在 order_payment 表中有2条记录，在 order_detail 表中有 5 条记录，2条orderId1的 3条orderId2 的
        // 不能直接从 order_payment 表查询，因为 公司团餐模式，支持一份订单从 多个不同店铺选择菜品，因此 order_payment表中没有 store_id; 后期可以考虑参考个人订单模式，限制一份订单的所有菜品只能从一个店铺选购
        Set<String> uniqueOrderIds = orderDetails.stream().map(OrderDetail::getOrderId).filter(StringUtils::isNotEmpty).collect(Collectors.toSet());
        log.warn("手动触发立即打印所有订单, 去重后的订单ids: {}", uniqueOrderIds);
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                log.warn("手动触发打印所有订单，开始打印 ...");
                for (String orderId : uniqueOrderIds) {

                    orderServiceImpl.printOrder(orderId);
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
                log.warn("手动触发打印所有订单，打印结束");
            }
        });
        thread.start();

        Result<Object> result = new Result<>();
        result.setCode(ResultCode.SUCCESS);
        result.setMessage("操作成功，正在打印中 ...");
        result.setData(Collections.emptyList());

        return result;
    }


    /**
     * 通过店主手机号查询店铺 ID（TODO: 当前团餐系统中，一个手机号对应一个店铺），该接口当前用于给个人团餐系统通过手机号获取团餐系统中店铺 ID
     *
     * @param phone 店主手机号
     * @return 店铺 ID
     */
    @GetMapping("/queryStoreIdByPhone")
    public Result<List<String>> queryStoreIdByPhone(@RequestParam("phone") String phone) {
        LambdaQueryWrapper<Store> queryWrapper = new LambdaQueryWrapper<Store>().eq(Store::getMerchantPhone, phone)
                .eq(Store::getDelFlag, false)
                .select(Store::getId);
        List<Store> storeList = storeMapper.selectList(queryWrapper);
        List<String> storeIdList = storeList.stream()
                .map(Store::getId)
                .collect(Collectors.toList());
        return ResultGenerator.genSuccessResult(storeIdList);
    }

}
